#include "clientoperation.h"
#include "jsonparse.h"
#include "requestfactory.h"
#include "tcpsocket.h"
#include "respondfactory.h"
#include "aescrypto.h"
#include <QDebug>
#include <QCryptographicHash>
#include <QThread>

ClientOperation::ClientOperation(QObject *parent) : QObject(parent)
{
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    m_shm = new SeckeyShm(conf.shmKey, 1);
}

ClientOperation::~ClientOperation()
{
    delete m_shm;
}

void ClientOperation::consultPrepare(RequestInfo &info, RsaCrypto &rsa, ClientOperation *obj)
{
    Q_UNUSED(obj)
    info.cmdtype = CmdType::Consult;
    info.data = rsa.publicPemKey();
}

void ClientOperation::consultDispose(RespondInfo &info, ClientOperation *obj)
{
    if(info.status)
    {
        emit obj->showMsg("使用私钥对服务器生成的秘钥解密...");
        // 初始化要回复的数据的格式: {"KeyID":"1111", "AesKey":"xxx"}
        // 将resInfo.data中的数据解析出来
        QString keyid = JsonParse::getInstance()->simpleObjValue(info.data, "KeyID");
        QString aeskey = JsonParse::getInstance()->simpleObjValue(info.data, "AesKey");

        // 9. 将得到的秘钥存储到共享内存中
        // 使用私钥解密
        // 秘钥对象是在客户端生成的, 并且秘钥信息已经写入到磁盘文件中了
        RsaCrypto rsa;
        QString pripem = rsa.privatePemKey();
        rsa.parseStringToKey(pripem, RsaCrypto::Private);
        QString key = rsa.rsaPriKeyDecrypt(aeskey);
        qDebug() << "对称加密的秘钥: " << key;
        emit obj->showMsg("对称加密的秘钥: " + key);
        // 将秘钥写入到共享内存中
        // keyID是在服务端生成的, 由服务器发送给客户端
        emit obj->showMsg("对称加密的秘钥写入到共享内存...");
        CliConfig conf = JsonParse::getInstance()->readClientConfig();
        SeckeyInfo keyinfo(true, keyid.toInt(), conf.clientID, conf.serverID, key);
        obj->m_shm->writeShm(keyinfo);
        emit obj->showMsg("秘钥协商完成, 并且成功, 恭喜 哈哈哈...");

        // 生成外联接口需要的配置文件
        // {"ServerID":"xx", "ClientID", "xx", "ShmKey":"xx"}
        QStringList keys, values;
        keys <<"ShmKey" << "ServerID" << "ClientID";
        values << conf.shmKey << conf.serverID << conf.clientID;
        JsonParse::getInstance()->writeInterfaceJson(keys, values);
    }
    else
    {
        emit obj->showMsg("秘钥协商完成, 失败了, 非常遗憾!!!");
    }
}

void ClientOperation::verifyPrepare(RequestInfo &info, RsaCrypto &rsa, ClientOperation *obj)
{
    info.cmdtype = CmdType::Verify;
    // info.data 中的数据格式: {"Hash":"xxx", "PubKey":"xxxxyyyzz"}
    QStringList keys, values;
    keys << "Hash" << "PubKey";
    // 读共享内存, 将秘钥读出, 然后及时出哈希值
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    SeckeyInfo keyinfo = obj->m_shm->readShm(conf.clientID, conf.serverID);
    QString sha224 = QCryptographicHash::hash(
                keyinfo.seckey, QCryptographicHash::Sha224);
    values << sha224 << rsa.publicPemKey();
    info.data = JsonParse::getInstance()->generateSimpleObj(keys, values);
}

void ClientOperation::verifyDispose(RespondInfo &info, ClientOperation *obj)
{
    if(info.status)
    {
        // 秘钥校验成功了, 客户端和服务器端使用的秘钥相同
        emit obj->showMsg("********** 恭喜, 秘钥校验成功了 **********");
    }
    else
    {
        // 秘钥校验失败了, 客户端和服务器端使用的秘钥不同
        emit obj->showMsg("********** Sorry, 秘钥校验失败了 **********");
    }
}

void ClientOperation::logoutPrepare(RequestInfo &info, RsaCrypto &rsa, ClientOperation *obj)
{
    info.cmdtype = CmdType::Logout;
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    SeckeyInfo keyInfo = obj->m_shm->readShm(conf.clientID, conf.serverID);
    QStringList keys, values;
    keys << "KeyID" << "Status" << "PubKey";
    values << QString::number(keyInfo.seckeyID)
           << QString::number(!keyInfo.status)
           << rsa.publicPemKey();
    info.data = JsonParse::getInstance()->generateSimpleObj(keys, values);

}

void ClientOperation::logoutDispose(RespondInfo &info, ClientOperation *obj)
{
    if(info.status)
    {
        // 修改秘钥协商客户端共享内存中秘钥状态
        CliConfig conf = JsonParse::getInstance()->readClientConfig();
        SeckeyInfo keyInfo = obj->m_shm->readShm(conf.clientID, conf.serverID);
        keyInfo.status = !keyInfo.status;
        obj->m_shm->writeShm(keyInfo);
        emit obj->showMsg("秘钥状态修改成功, 恭喜...");
    }
    else
    {
         emit obj->showMsg("秘钥状态修改失败, 非常遗憾...");
    }
}

void ClientOperation::loginPrepare(RequestInfo &info, RsaCrypto &rsa, ClientOperation *obj)
{
    info.cmdtype = CmdType::Login;

    QStringList keys, values;
    keys << "PubKey" << "NetID" << "Password";
    values << rsa.publicPemKey() << obj->m_id << obj->m_pwd;

    // {"PubKey":"xxxxyyyzz", "NetID":"xxx", "Password":"1"}
    info.data = JsonParse::getInstance()->generateSimpleObj(keys, values);

    // 网点的ID和密码是明文传输的, 在传输过程中有可能会泄露, 可以对其加密
    // 使用对称加密的方式, 秘钥在共享内存中(登录之前先进行一个秘钥协商得到秘钥)
    // 读共享内存
    SeckeyInfo keyinfo = obj->m_shm->readShm(info.clientID, info.serverID);
    // 创建对称加密对象
    AesCrypto aes(keyinfo.seckey);
    info.data = aes.aesCBCEncrypt(info.data);
}

void ClientOperation::loginDispose(RespondInfo &info, ClientOperation *obj)
{
    if(info.status)
    {
        // 发送信号, 通过登录窗口可以登录
        emit obj->loginSuccess();
    }
    else
    {
        // 发送信号, 通过登录窗口不可以登录
        emit obj->loginFail();
    }
}

void ClientOperation::communication(void *arg)
{
    ArgInfo* arginfo = (ArgInfo*)arg;
    // ClientOperation* cliObj = (ClientOperation*)arg;
    // 1. 生成非对称加密的密钥对 RSA
    arginfo->object->showMsg("正在生成RSA的密钥对...");
    RsaCrypto rsa;
    rsa.generateRsakey(2048);   // 秘钥长度 2048 bit

    arginfo->object->showMsg("正在进行数据的初始化...");
    // 2. 初始化要发送的请求数据
    RequestInfo info;
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    info.clientID = conf.clientID;
    info.serverID = conf.serverID;

    /////////////////////////////////////////////////
    // 对data数据的初始化方式不同
    // 处理动作需要通过参数传递到该函数中 --> 参数是函数指针
    arginfo->prepare(info, rsa, arginfo->object);
    /////////////////////////////////////////////////

    // 因为data中的数据可长可短, 根据实际情况变化, 不能控制
    // 可以对data先进行哈希计算, 然后在签名
    QByteArray sha1 = QCryptographicHash::hash(
                info.data.toUtf8(), QCryptographicHash::Sha1);
    info.sign = rsa.rsaSign(sha1);   // 对data进行签名

    arginfo->object->showMsg("开始对待发送数据进行序列化处理...");
    // 3. 序列化要发送的数据块
    // 创建工厂类对象
    CodecFactory* f = new RequestFactory(&info);
    // 创建编解码对象
    Codec* c = f->createCodec();
    // 进行数据的编码(序列化)
    QString msg = c->encodeMsg();
    // 释放资源
    delete f;
    delete c;

    // 4. 套接字连接, 当前角色的客户端, 连接秘钥协商的服务器
    TcpSocket* tcp = new TcpSocket;
    do
    {
        if(tcp == NULL)
        {
            qDebug() << "创建套接字对象失败, 拜拜...";
            break;
        }
        int ret = tcp->connectToHost(conf.ip, conf.port.toInt());
        if(ret != 0)
        {
            qDebug() << "连接服务器失败, ...";
            arginfo->object->showMsg("连接服务器失败了, 拜拜...");
            break;
        }
        qDebug() << "连接服务器成功了, 哈哈...";
        arginfo->object->showMsg("连接服务器成功了...");

        // 5. 连接成功之后发送序列化之后的字符串数据
        arginfo->object->showMsg("给服务器发送请求数据...");
        tcp->sendMsg(msg);

        arginfo->object->showMsg("接收服务器回复的数据...");
        // 6. 等待并接收服务器回复的请求数据
        msg = tcp->recvMsg();

        arginfo->object->showMsg("开始对数据进行反序列化处理...");
        // 7. 对接收的回复数据反序列化(得到原始数据)
        // 创建工厂类对象
        f = new RespondFactory(msg);
        // 创建编解码对象
        c = f->createCodec();
        // 进行数据的编码(序列化)
        RespondInfo resInfo;
        c->decodeMsg(&resInfo);
        // 8. 如果服务器对请求处理成功了, 取出加密之后的秘钥, 并且解密
        ///////////////////////////////////////////////////////////
        // 不同的请求, 服务器回复的数据对应的实际含义也不同, 需要单独处理
        // 处理动作需要通过参数传递到该函数中 --> 参数是函数指针
        arginfo->dispose(resInfo, arginfo->object);
        ///////////////////////////////////////////////////////////
    }while(0);

    arginfo->object->showMsg(" ************ The End ************ ");
    arginfo->object->showMsg("  ");
    // 10. 释放资源
    // 秘钥协商客户端和秘钥协商服务器的连接是一个短连接(通信的时候发起连接, 通信完成断开连接)
    // 因为秘钥协商的频率相对较低, 必须要一直通信
    if(tcp)
    {
        tcp->disConnect();
        delete tcp;
    }
    if(f) delete f;
    if(c) delete c;
    delete arginfo;
}

void ClientOperation::lastTask(void *arg)
{
    // 睡3s
    // sleep() 让线程阻塞和 条件变量让线程阻塞本质是不同的
    // 睡3s是在执行任务
    QThread::sleep(3);
}

void ClientOperation::saveLoginInfo(QString id, QString pwd)
{
    m_id = id;
    m_pwd = pwd;
}
